package com.gis.handler;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.gis.dto.Task;
import com.gis.dto.ScriptDTO;
import com.gis.entity.ScriptData;
import com.gis.result.ResponseResult;
import com.gis.service.LayerService;
import com.gis.service.ScriptService;
import com.gis.vo.TaskVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.Files;
import java.io.IOException;
import java.util.Comparator;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;


@Slf4j
@Component
@RequiredArgsConstructor
public class WebSocketHandler extends TextWebSocketHandler {
    @Value("${files.dir}/${files.relative-save-dir}/${files.task}")
    private String taskDir;
    private final ScriptService scriptService;
    private final LayerService layerService;
    private final ObjectMapper objectMapper = new ObjectMapper();
    private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);



    /**
     * 当有客户端连接时调用
     * @param session
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session){
        log.info("WebSocket 连接已打开：");
    }



    /**
     * 当连接关闭时调用
     * @param session
     * @param status
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status){
        log.info("WebSocket 连接已关闭：" + status);
    }



    /**
     * 当有消息从客户端发送过来时调用
     * @param session
     * @param message
     * @throws Exception
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) {
        log.info("收到客户端消息: " + message.getPayload());
        // 生成任务ID
        String uuid = UUID.randomUUID().toString().replace("-", "");
        TaskVO taskVO = new TaskVO();
        taskVO.setTaskId(uuid);
        try {
            // 解析 JSON
            String payload = message.getPayload();
            Task task = objectMapper.readValue(payload, Task.class);
            log.info("收到客户端Task: " + task);
            task.getNodes().sort(Comparator.comparing(ScriptDTO::getSerialNum));
            //判断这个流程对不对
            if(!isTaskCurrect(task)){
                sendErrorMessage(session, taskVO, "任务流程不合规");
                return;
            }
            // 创建目录
            Path taskPath = Paths.get(taskDir, uuid+"/result").toAbsolutePath();
            File taskDirectory = taskPath.toFile();
            if (!taskDirectory.exists() && !taskDirectory.mkdirs()) {
                sendFailedMessage(session, taskVO, "创建任务目录失败: " + taskPath);
                return;
            }
            // 执行任务
            executeTask(task, session, taskVO);
        } catch (com.fasterxml.jackson.core.JsonProcessingException e) {
            sendFailedMessage(session, taskVO, "JSON解析失败：" + e.getMessage());
        } catch (IOException e) {
            sendFailedMessage(session, taskVO, "文件操作失败：" + e.getMessage());
        } catch (Exception e) {
            sendFailedMessage(session, taskVO, "服务器内部错误：" + e.getMessage());
        }
    }


    /**
     * 判断任务是否合规
     * @param task
     * @return
     */
    private boolean isTaskCurrect(Task task){
        int inputNum = task.getLayers().size();
        String inputType = layerService.getById(task.getLayers().get(0)).getFileType();
        for(int i=0;i<task.getNodes().size();i++){
            ScriptData scriptData = scriptService.getById(task.getNodes().get(i).getScriptId());
            String inputTypeScript = scriptData.getInputType();
            Integer inputNumScript = scriptData.getInputNum();
            if(inputNum!=inputNumScript||!inputType.equals(inputTypeScript)){
                return false;
            }
            inputNum = scriptData.getOutputNum();
            inputType = scriptData.getOutputType();
        }
        return inputNum == task.getOutputNames().size();
    }


    /**
     * 线程安全？
     * @param session
     * @param message
     */
    private synchronized void sendMessageSafely(WebSocketSession session, String message) {
        try {
            session.sendMessage(new TextMessage(message));
        } catch (IOException e) {
            log.error("WebSocket 发送消息失败", e);
        }
    }

    /**
     * 发送错误消息
     * @param session
     * @param taskVO
     * @param errorMessage
     */
    private void sendFailedMessage(WebSocketSession session, TaskVO taskVO, String errorMessage) {
        try {
            taskVO.setCurrentStatus("FAILED");
            taskVO.setMessage(errorMessage);

            ResponseResult<TaskVO> responseResult = ResponseResult.fail(taskVO);
            String jsonResponse = objectMapper.writeValueAsString(responseResult);
            sendMessageSafely(session, jsonResponse);

            log.error("任务失败: " + errorMessage);
        } catch (IOException e) {
            log.error("发送错误消息失败: " + e.getMessage());
        }
    }
    private void sendErrorMessage(WebSocketSession session, TaskVO taskVO, String errorMessage) {
        try {
            taskVO.setCurrentStatus("ERROR");
            taskVO.setMessage(errorMessage);

            ResponseResult<TaskVO> responseResult = ResponseResult.fail(taskVO);
            String jsonResponse = objectMapper.writeValueAsString(responseResult);
            sendMessageSafely(session, jsonResponse);

            log.error("任务失败: " + errorMessage);
        } catch (IOException e) {
            log.error("发送错误消息失败: " + e.getMessage());
        }
    }



    /**
     * 服务器向前端发送消息
     * @param session
     * @param taskVO
     * @param scriptIndex
     * @param status
     * @param message
     */
    private void sendTaskUpdate(WebSocketSession session, TaskVO taskVO, Integer scriptIndex, String status, String message) {
        try {
            if(taskVO!=null&&scriptIndex!=null) taskVO.setCurrentScript(scriptIndex);
            if(taskVO!=null&&status!=null) taskVO.setCurrentStatus(status);
            if(taskVO!=null&&message!=null) taskVO.setMessage(message);
            ResponseResult<TaskVO> responseResult;
            if ("FAILED".equals(status)||"ERROR".equals(status)){
                responseResult = ResponseResult.fail(taskVO);
            }else{
                responseResult = ResponseResult.success(taskVO);
            }
            String jsonResponse = objectMapper.writeValueAsString(responseResult);
            sendMessageSafely(session, jsonResponse);
            // 延迟删除临时任务目录
            if ("FINISHED".equals(status)||"FAILED".equals(status)||"ERROR".equals(status)) {
                String taskPath = taskDir + "/" + taskVO.getTaskId();
                scheduleDirectoryDeletion(taskPath);
                session.close();
            }
        } catch (IOException e) {
            log.error("发送消息失败: " + e.getMessage());
        }
    }


    /**
     * 延迟删除临时任务目录
     * @param taskPath
     */
    private void scheduleDirectoryDeletion(String taskPath) {
        scheduler.schedule(() -> {
            try {
                Path path = Paths.get(taskPath.trim());
                if (!Files.exists(path)) {
                    log.warn("任务目录不存在: " + taskPath);
                    return;
                }
                if (!Files.isDirectory(path)) {
                    log.warn("任务路径不是目录: " + taskPath);
                    return;
                }
                if (!Files.isWritable(path)) {
                    log.error("没有权限删除目录: " + taskPath);
                    return;
                }
                Files.walk(path)
                        //先删子文件 逆序排序
                        .sorted(Comparator.reverseOrder())
                        //反射 path-file map
                        .map(Path::toFile)
                        //反射 删除方法
                        .forEach(File::delete);
                log.info("已删除任务目录: " + taskPath);
            } catch (IOException e) {
                log.error("删除任务目录失败: " + taskPath, e);
            }
        }, 30, TimeUnit.MINUTES);
    }


    /**
     * 执行任务
     * @param task
     * @param session
     * @param taskVO
     */
    private void executeTask(Task task, WebSocketSession session, TaskVO taskVO) {
        new Thread(() -> {
            try {
                String taskPath = taskDir + "/" + taskVO.getTaskId();
                sendTaskUpdate(session, taskVO, -1, "STARTED", "任务开始执行");

                List<String> inputPathList = new ArrayList<>();
                List<String> outputPathList = new ArrayList<>();

                List<ScriptData> scripts = task.getNodes().stream()
                        .map(scriptDTO -> scriptService.getById(scriptDTO.getScriptId()))
                        .toList();

                
                if (scripts.size() == 1) {
                    // 只有一个算法，直接执行
                    ScriptData scriptData = scripts.get(0);
                    String outputType = scriptData.getOutputType();
                    sendTaskUpdate(session, taskVO, scriptData.getId(), "RUNNING", "执行第 " + 1 + " 个脚本");
                    prepareInputPaths(task, inputPathList);
                    prepareOutputPaths(task, taskPath, outputPathList, outputType);

                    if (!runScript(scriptData, inputPathList, outputPathList, session, taskVO)) {
                        return;
                    }
                } else {
                    // 多个算法，执行流水线
                    for (int i = 0; i < scripts.size(); i++) {
                        ScriptData scriptData = scripts.get(i);
                        String outputType = scriptData.getOutputType();
                        sendTaskUpdate(session, taskVO, scriptData.getId(), "RUNNING", "执行第 " + (i + 1) + " 个脚本");

                        if (i == 0) {
                            // 第一个算法：输入是用户数据，输出是中间态
                            prepareInputPaths(task, inputPathList);
                            prepareIntermediateOutputPaths(taskPath, outputPathList, scriptData.getOutputNum(), i, outputType);
                        } else if (i == scripts.size() - 1) {
                            // 最后一个算法：输入是上一步的输出，输出是用户需要的数据
                            inputPathList = new ArrayList<>(outputPathList);
                            outputPathList.clear();
                            prepareOutputPaths(task, taskPath, outputPathList, outputType);
                        } else {
                            // 中间算法：输入是上一步的输出，输出是中间态
                            inputPathList = new ArrayList<>(outputPathList);
                            outputPathList.clear();
                            prepareIntermediateOutputPaths(taskPath, outputPathList, scriptData.getOutputNum(), i, outputType);
                        }

                        if (!runScript(scriptData, inputPathList, outputPathList, session, taskVO)) {
                            return;
                        }
                    }
                }

                sendTaskUpdate(session, taskVO, -1, "FINISHED", "任务执行完成");
            } catch (Exception e) {
                sendTaskUpdate(session, taskVO, -1, "FAILED", "任务执行出错: " + e.getMessage());
            }
        }).start();
    }


    /**
     * 解析输入路径（用户输入的数据）
     */
    private void prepareInputPaths(Task task, List<String> inputPathList) {
        for (Integer layerId : task.getLayers()) {
            String inputPath = layerService.getById(layerId).getSavedDir() + "/" +
                    layerService.getById(layerId).getSavedName();
            inputPathList.add(inputPath);
        }
    }

    /**
     * 解析输出路径（最终输出文件）
     */
    private void prepareOutputPaths(Task task, String taskPath, List<String> outputPathList, String outputType) {
        for (String outputName : task.getOutputNames()) {
            String outputPath = taskPath + "/result/" + outputName + "." + outputType;
            outputPathList.add(outputPath);
        }
    }

    /**
     * 解析中间态输出路径
     */
    private void prepareIntermediateOutputPaths(String taskPath, List<String> outputPathList, int outputNum, int step, String outputType) {
        for (int j = 0; j < outputNum; j++) {
            String outputPath = taskPath + "/" + "layer" + step + "_" + j + "." + outputType;
            outputPathList.add(outputPath);
        }
    }

    /**
     * 运行一个脚本
     */
    private boolean runScript(ScriptData scriptData, List<String> inputPathList, List<String> outputPathList,
                              WebSocketSession session, TaskVO taskVO) {
        if (scriptData.getInputNum() != inputPathList.size() || scriptData.getOutputNum() != outputPathList.size()) {
            sendTaskUpdate(session, taskVO, scriptData.getId(), "FAILED", "脚本输入输出无法对应");
            return false;
        }

        String scriptPath = scriptData.getSavedDir() + "/" + scriptData.getOriginName().replace(".exe", "")+"/"+scriptData.getOriginName();

        boolean success = executeScript(inputPathList, scriptPath, outputPathList);
        if (!success) {
            sendTaskUpdate(session, taskVO, scriptData.getId(), "FAILED", "脚本执行失败");
        }
        return success;
    }



    /**
     * 执行脚本 根据信息构造脚本命令 执行生成输出文件
     * @param inputPath     输入文件路径 这里如果是流程中间状态文件是不会保存的 所以必须传路径
     * @param scriptPath    脚本路径
     * @param outputPath    输出文件路径
     */
    private boolean executeScript(List<String> inputPath, String scriptPath, List<String> outputPath) {
        // 2 构造脚本命令
        List<String> command = new ArrayList<>();
        // 确保可执行文件路径是正确的
        File scriptFile = new File(scriptPath);
        if (!scriptFile.exists()) {
            log.info("脚本文件不存在：" + scriptPath);
            return false;
        }
        command.add(scriptPath);
        for(int i=1;i<=inputPath.size();i++){
            String inputNum = String.format("%02d", i);
            command.add("Sat"+inputNum);
            command.add(inputPath.get(i-1));
        }
        command.addAll(outputPath);
        log.info("执行命令：" + String.join(" ", command));
        // 3 执行命令
        try {
            ProcessBuilder processBuilder = new ProcessBuilder(command);
            processBuilder.redirectErrorStream(true); // 合并错误输出
            Process process = processBuilder.start();
            //读取输出
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            int exitCode = process.waitFor();
            return exitCode == 0 && reader.readLine() == null;
        } catch (IOException | InterruptedException e) {
            log.info(e.getMessage());
            return false;
        }
    }
}
